ci: wire-shape contract gate (QF-14) — block Java @JsonProperty drift from OpenAPI#137
Closed
saurabhjain1592 wants to merge 3 commits into
Closed
ci: wire-shape contract gate (QF-14) — block Java @JsonProperty drift from OpenAPI#137saurabhjain1592 wants to merge 3 commits into
saurabhjain1592 wants to merge 3 commits into
Conversation
Adds a CI gate that fails any PR introducing drift between Java @JsonProperty annotations and the OpenAPI specs pinned at tests/fixtures/wire-shape-baseline.json::openapi_specs_sha. Four gates: 1. Cross-spec schema divergence — same schema name declared with different shapes across spec files. 2. Intra-file duplicates — same schema name declared twice in one spec file (PolicyMatch in orchestrator-api.yaml is the existing example, baselined for now). 3. Per-type SDK-vs-spec drift — wire field names diff between Java @JsonProperty and the spec, baseline-aware. 4. Registered-type rename-escape — types in the baseline that disappear from either side fail the gate. The pinned spec SHA is itself guarded by a `spec-pin-bump` PR label so a single PR can't both move the SHA and silence drift. Source discovery walks brace depth on string- and comment-stripped text so annotations are attributed to the innermost enclosing class/record/interface/enum rather than the file's outer class. Without this, types like WorkflowTypes.CreateWorkflowRequest and WorkflowTypes.RetryContext (10+ wire types nested inside the WorkflowTypes namespace class) would silently escape coverage. Initial baseline at SHA bf1ca22: - 70 registered Java<->OpenAPI type pairs - 39 per-type drift entries (burndown follow-ups) - 8 cross-spec divergences (platform-side reconciliation tracked separately) - 1 intra-file duplicate (PolicyMatch) Mirrors the Python, Go, and TypeScript wire-shape gates. Re-baseline: python3 scripts/wire_shape/refresh.py /path/to/community/docs/api
- load_baseline raises SystemExit with regen hint when the JSON file
is malformed instead of dumping an opaque traceback.
- write_baseline cleans up its tmp sidecar on any exception so a
crashed run can't poison the next refresh from the same PID.
- The validator now exits 1 (not 0) when AXONFLOW_OPENAPI_SPECS_DIR
is set but doesn't point at a directory. A misconfigured CI step
silently disabling the gate produces a green check on a non-running
validator, which we refuse to do. An unset env still skips with 0.
- Workflow's SHA-bump guard distinguishes "baseline missing on base
branch" (genuine first-pin introduction) from "baseline present
but unparseable" (bypass attempt). A malformed baseline on the
base branch can no longer route a labelless PR through the
first-pin path.
- Java source parser:
* Recognises Java 15+ text blocks (`"""..."""`). Previously the
first `"""` looked like quote-empty-quote and the body parsed
as live source — fake `@JsonProperty(...)` and `class X {`
inside a text block silently corrupted attribution.
* Filters annotation matches whose position falls inside a
blanked range (string, comment, text block) so the wire-name
regex on raw content can't grab annotations the structural
pass already nullified.
* Attributes record-parameter annotations to the record. Pending
decls are now tracked between their token and the body `{`,
so `record Foo(@JsonProperty("x") int x) {}` no longer drops
the wire name. The SDK has no records today; this is forward
defence for the next type-class refactor.
- Documents the remaining anonymous-inner-class limitation (no
`class` keyword, so annotations would leak to the enclosing type).
The SDK does not put @JsonProperty inside anonymous inners.
Synthetic-fixture tests cover record params, text blocks with fake
annotations, line and block comments, nested classes, and enum
values. Real Java SDK baseline unchanged: 70 registered types, 39
drift, 8 cross-spec, 1 intra-file.
Gate 1 (cross-spec divergence) only iterated currently observed schemas. A baselined divergence that the platform has since reconciled silently lingered in the baseline forever; the same old incompatible shape could be reintroduced and pass the gate because its fingerprint matched a stale entry that should have been deleted. Adds the reverse pass that Gate 2 already does for intra-file duplicates: any baselined cross-spec name that is no longer observed in the current specs fails the run with a pointer at the specific baseline key to delete. Verified locally: - Positive run on clean baseline still exits 0. - Adding a phantom 'PhantomDivergence' entry to baseline.cross_spec_ duplicates causes the validator to exit 1 with a clear "remove from baseline" message naming the stale key.
Member
Author
|
Closing as superseded. The wire-shape contract gate this PR was opening landed on main via the parallel sweep PR #138 (which bundled the gate with the type alignment work). The CONTRIBUTING.md scaffold landed via #139 (burndown policy). Re-merging this branch would:
The QF-14 work is shipped — verifying with |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a CI gate that blocks any PR introducing drift between Java
@JsonPropertyannotations and the OpenAPI specs pinned viatests/fixtures/wire-shape-baseline.json::openapi_specs_sha.Mirrors the Python (QF-15), Go (QF-11), and TypeScript (QF-12) wire-shape gates already shipped in those SDKs.
Four gates
PolicyMatchinorchestrator-api.yamlis currently baselined.@JsonPropertyand the spec, baseline-aware. 39 baselined.The pinned spec SHA itself is guarded by a
spec-pin-bumpPR label so a single PR can't both move the SHA and silence drift.Initial baseline at SHA
bf1ca22PolicyMatch)Why brace-depth parsing
WorkflowTypes.javaalone holds 10+ wire types nested inside an outer namespace class, plus 6 inner enums with@JsonPropertyon their values. A naive regex that captures only the outermost class per file silently attributes 91 mixed annotations toWorkflowTypes, missing genuine wire types likeCreateWorkflowRequest,StepGateRequest,RetryContext,StepGateResponse,WorkflowStatus,GateDecision,MediaGovernanceConfig. Source discovery walks brace depth on string- and comment-stripped text so annotations land on the innermost enclosing class/record/interface/enum.Re-baselining
Atomic write (temp + rename) so a mid-encode crash can't poison the baseline.
Test plan
@JsonPropertyis renamed to introduce drift (negative case across nested types:RetryContext,StepGateRequest,MarkStepCompletedRequest)AXONFLOW_OPENAPI_SPECS_DIRis unset / not a directoryWorkflowTypes.CreateWorkflowRequest) and inner enums (WorkflowStatus,GateDecision)spec-pin-bumplabel exists on this repo (created in this PR)